<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head>

<meta charset="utf-8">
<meta name="generator" content="quarto-1.7.33">

<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">


<title>15&nbsp; Project 4 - Developing an image filter – Introduction to Zig</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
div.columns{display: flex; gap: min(4vw, 1.5em);}
div.column{flex: auto; overflow-x: auto;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
ul.task-list li input[type="checkbox"] {
  width: 0.8em;
  margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */ 
  vertical-align: middle;
}
/* CSS for syntax highlighting */
html { -webkit-text-size-adjust: 100%; }
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
  { counter-reset: source-line 0; }
pre.numberSource code > span
  { position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
  { content: counter(source-line);
    position: relative; left: -1em; text-align: right; vertical-align: baseline;
    border: none; display: inline-block;
    -webkit-touch-callout: none; -webkit-user-select: none;
    -khtml-user-select: none; -moz-user-select: none;
    -ms-user-select: none; user-select: none;
    padding: 0 4px; width: 4em;
  }
pre.numberSource { margin-left: 3em;  padding-left: 4px; }
div.sourceCode
  {   }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
/* CSS for citations */
div.csl-bib-body { }
div.csl-entry {
  clear: both;
  margin-bottom: 0em;
}
.hanging-indent div.csl-entry {
  margin-left:2em;
  text-indent:-2em;
}
div.csl-left-margin {
  min-width:2em;
  float:left;
}
div.csl-right-inline {
  margin-left:2em;
  padding-left:1em;
}
div.csl-indent {
  margin-left: 2em;
}</style>


<script src="../site_libs/quarto-nav/quarto-nav.js"></script>
<script src="../site_libs/quarto-nav/headroom.min.js"></script>
<script src="../site_libs/clipboard/clipboard.min.js"></script>
<script src="../site_libs/quarto-search/autocomplete.umd.js"></script>
<script src="../site_libs/quarto-search/fuse.min.js"></script>
<script src="../site_libs/quarto-search/quarto-search.js"></script>
<meta name="quarto:offset" content="../">
<link href="../Chapters/14-threads.html" rel="next">
<link href="../Chapters/14-zig-c-interop.html" rel="prev">
<script src="../site_libs/quarto-html/quarto.js" type="module"></script>
<script src="../site_libs/quarto-html/tabsets/tabsets.js" type="module"></script>
<script src="../site_libs/quarto-html/popper.min.js"></script>
<script src="../site_libs/quarto-html/tippy.umd.min.js"></script>
<script src="../site_libs/quarto-html/anchor.min.js"></script>
<link href="../site_libs/quarto-html/tippy.css" rel="stylesheet">
<link href="../site_libs/quarto-html/quarto-syntax-highlighting-59898bd1c6b9d2bb783127feaa000c76.css" rel="stylesheet" class="quarto-color-scheme" id="quarto-text-highlighting-styles">
<link href="../site_libs/quarto-html/quarto-syntax-highlighting-dark-d329e753491efaeac79c98c4b193a686.css" rel="stylesheet" class="quarto-color-scheme quarto-color-alternate" id="quarto-text-highlighting-styles">
<link href="../site_libs/quarto-html/quarto-syntax-highlighting-59898bd1c6b9d2bb783127feaa000c76.css" rel="stylesheet" class="quarto-color-scheme-extra" id="quarto-text-highlighting-styles">
<script src="../site_libs/bootstrap/bootstrap.min.js"></script>
<link href="../site_libs/bootstrap/bootstrap-icons.css" rel="stylesheet">
<link href="../site_libs/bootstrap/bootstrap-5a7d69291c2a8c67bc3a157f4354e263.min.css" rel="stylesheet" append-hash="true" class="quarto-color-scheme" id="quarto-bootstrap" data-mode="light">
<link href="../site_libs/bootstrap/bootstrap-dark-a459d9a911ec262923466a12d18bc01e.min.css" rel="stylesheet" append-hash="true" class="quarto-color-scheme quarto-color-alternate" id="quarto-bootstrap" data-mode="dark">
<link href="../site_libs/bootstrap/bootstrap-5a7d69291c2a8c67bc3a157f4354e263.min.css" rel="stylesheet" append-hash="true" class="quarto-color-scheme-extra" id="quarto-bootstrap" data-mode="light">
<script id="quarto-search-options" type="application/json">{
  "location": "sidebar",
  "copy-button": false,
  "collapse-after": 3,
  "panel-placement": "start",
  "type": "textbox",
  "limit": 50,
  "keyboard-shortcut": [
    "f",
    "/",
    "s"
  ],
  "show-item-context": false,
  "language": {
    "search-no-results-text": "No results",
    "search-matching-documents-text": "matching documents",
    "search-copy-link-title": "Copy link to search",
    "search-hide-matches-text": "Hide additional matches",
    "search-more-match-text": "more match in this document",
    "search-more-matches-text": "more matches in this document",
    "search-clear-button-title": "Clear",
    "search-text-placeholder": "",
    "search-detached-cancel-button-title": "Cancel",
    "search-submit-button-title": "Submit",
    "search-label": "Search"
  }
}</script>
<script async="" src="https://www.googletagmanager.com/gtag/js?id=G-6CHJXK4CEV"></script>

<script type="text/javascript">

window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-6CHJXK4CEV', { 'anonymize_ip': true});
</script>

  <script src="https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=es6"></script>
  <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js" type="text/javascript"></script>

<script type="text/javascript">
const typesetMath = (el) => {
  if (window.MathJax) {
    // MathJax Typeset
    window.MathJax.typeset([el]);
  } else if (window.katex) {
    // KaTeX Render
    var mathElements = el.getElementsByClassName("math");
    var macros = [];
    for (var i = 0; i < mathElements.length; i++) {
      var texText = mathElements[i].firstChild;
      if (mathElements[i].tagName == "SPAN") {
        window.katex.render(texText.data, mathElements[i], {
          displayMode: mathElements[i].classList.contains('display'),
          throwOnError: false,
          macros: macros,
          fleqn: false
        });
      }
    }
  }
}
window.Quarto = {
  typesetMath
};
</script>

</head>

<body class="nav-sidebar floating quarto-light"><script id="quarto-html-before-body" type="application/javascript">
    const toggleBodyColorMode = (bsSheetEl) => {
      const mode = bsSheetEl.getAttribute("data-mode");
      const bodyEl = window.document.querySelector("body");
      if (mode === "dark") {
        bodyEl.classList.add("quarto-dark");
        bodyEl.classList.remove("quarto-light");
      } else {
        bodyEl.classList.add("quarto-light");
        bodyEl.classList.remove("quarto-dark");
      }
    }
    const toggleBodyColorPrimary = () => {
      const bsSheetEl = window.document.querySelector("link#quarto-bootstrap:not([rel=disabled-stylesheet])");
      if (bsSheetEl) {
        toggleBodyColorMode(bsSheetEl);
      }
    }
    const setColorSchemeToggle = (alternate) => {
      const toggles = window.document.querySelectorAll('.quarto-color-scheme-toggle');
      for (let i=0; i < toggles.length; i++) {
        const toggle = toggles[i];
        if (toggle) {
          if (alternate) {
            toggle.classList.add("alternate");
          } else {
            toggle.classList.remove("alternate");
          }
        }
      }
    };
    const toggleColorMode = (alternate) => {
      // Switch the stylesheets
      const primaryStylesheets = window.document.querySelectorAll('link.quarto-color-scheme:not(.quarto-color-alternate)');
      const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate');
      manageTransitions('#quarto-margin-sidebar .nav-link', false);
      if (alternate) {
        // note: dark is layered on light, we don't disable primary!
        enableStylesheet(alternateStylesheets);
        for (const sheetNode of alternateStylesheets) {
          if (sheetNode.id === "quarto-bootstrap") {
            toggleBodyColorMode(sheetNode);
          }
        }
      } else {
        disableStylesheet(alternateStylesheets);
        enableStylesheet(primaryStylesheets)
        toggleBodyColorPrimary();
      }
      manageTransitions('#quarto-margin-sidebar .nav-link', true);
      // Switch the toggles
      setColorSchemeToggle(alternate)
      // Hack to workaround the fact that safari doesn't
      // properly recolor the scrollbar when toggling (#1455)
      if (navigator.userAgent.indexOf('Safari') > 0 && navigator.userAgent.indexOf('Chrome') == -1) {
        manageTransitions("body", false);
        window.scrollTo(0, 1);
        setTimeout(() => {
          window.scrollTo(0, 0);
          manageTransitions("body", true);
        }, 40);
      }
    }
    const disableStylesheet = (stylesheets) => {
      for (let i=0; i < stylesheets.length; i++) {
        const stylesheet = stylesheets[i];
        stylesheet.rel = 'disabled-stylesheet';
      }
    }
    const enableStylesheet = (stylesheets) => {
      for (let i=0; i < stylesheets.length; i++) {
        const stylesheet = stylesheets[i];
        if(stylesheet.rel !== 'stylesheet') { // for Chrome, which will still FOUC without this check
          stylesheet.rel = 'stylesheet';
        }
      }
    }
    const manageTransitions = (selector, allowTransitions) => {
      const els = window.document.querySelectorAll(selector);
      for (let i=0; i < els.length; i++) {
        const el = els[i];
        if (allowTransitions) {
          el.classList.remove('notransition');
        } else {
          el.classList.add('notransition');
        }
      }
    }
    const isFileUrl = () => {
      return window.location.protocol === 'file:';
    }
    const hasAlternateSentinel = () => {
      let styleSentinel = getColorSchemeSentinel();
      if (styleSentinel !== null) {
        return styleSentinel === "alternate";
      } else {
        return false;
      }
    }
    const setStyleSentinel = (alternate) => {
      const value = alternate ? "alternate" : "default";
      if (!isFileUrl()) {
        window.localStorage.setItem("quarto-color-scheme", value);
      } else {
        localAlternateSentinel = value;
      }
    }
    const getColorSchemeSentinel = () => {
      if (!isFileUrl()) {
        const storageValue = window.localStorage.getItem("quarto-color-scheme");
        return storageValue != null ? storageValue : localAlternateSentinel;
      } else {
        return localAlternateSentinel;
      }
    }
    const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => {
      const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light';
      const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark';
      let newTheme = '';
      if(authorPrefersDark) {
        newTheme = isAlternate ? baseTheme : alternateTheme;
      } else {
        newTheme = isAlternate ? alternateTheme : baseTheme;
      }
      const changeGiscusTheme = () => {
        // From: https://github.com/giscus/giscus/issues/336
        const sendMessage = (message) => {
          const iframe = document.querySelector('iframe.giscus-frame');
          if (!iframe) return;
          iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app');
        }
        sendMessage({
          setConfig: {
            theme: newTheme
          }
        });
      }
      const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null;
      if (isGiscussLoaded) {
        changeGiscusTheme();
      }
    };
    const authorPrefersDark = false;
    const darkModeDefault = authorPrefersDark;
      document.querySelector('link#quarto-text-highlighting-styles.quarto-color-scheme-extra').rel = 'disabled-stylesheet';
      document.querySelector('link#quarto-bootstrap.quarto-color-scheme-extra').rel = 'disabled-stylesheet';
    let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default';
    // Dark / light mode switch
    window.quartoToggleColorScheme = () => {
      // Read the current dark / light value
      let toAlternate = !hasAlternateSentinel();
      toggleColorMode(toAlternate);
      setStyleSentinel(toAlternate);
      toggleGiscusIfUsed(toAlternate, darkModeDefault);
      window.dispatchEvent(new Event('resize'));
    };
    // Switch to dark mode if need be
    if (hasAlternateSentinel()) {
      toggleColorMode(true);
    } else {
      toggleColorMode(false);
    }
  </script>

<div id="quarto-search-results"></div>
  <header id="quarto-header" class="headroom fixed-top">
  <nav class="quarto-secondary-nav">
    <div class="container-fluid d-flex">
      <button type="button" class="quarto-btn-toggle btn" data-bs-toggle="collapse" role="button" data-bs-target=".quarto-sidebar-collapse-item" aria-controls="quarto-sidebar" aria-expanded="false" aria-label="Toggle sidebar navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">
        <i class="bi bi-layout-text-sidebar-reverse"></i>
      </button>
        <nav class="quarto-page-breadcrumbs" aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="../Chapters/13-image-filter.html"><span class="chapter-number">15</span>&nbsp; <span class="chapter-title">Project 4 - Developing an image filter</span></a></li></ol></nav>
        <a class="flex-grow-1" role="navigation" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item" aria-controls="quarto-sidebar" aria-expanded="false" aria-label="Toggle sidebar navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">      
        </a>
      <button type="button" class="btn quarto-search-button" aria-label="Search" onclick="window.quartoOpenSearch();">
        <i class="bi bi-search"></i>
      </button>
    </div>
  </nav>
</header>
<!-- content -->
<div id="quarto-content" class="quarto-container page-columns page-rows-contents page-layout-article">
<!-- sidebar -->
  <nav id="quarto-sidebar" class="sidebar collapse collapse-horizontal quarto-sidebar-collapse-item sidebar-navigation floating overflow-auto">
    <div class="pt-lg-2 mt-2 text-left sidebar-header">
    <div class="sidebar-title mb-0 py-0">
      <a href="../">Introduction to Zig</a> 
        <div class="sidebar-tools-main">
  <a href="" class="quarto-color-scheme-toggle quarto-navigation-tool  px-1" onclick="window.quartoToggleColorScheme(); return false;" title="Toggle dark mode"><i class="bi"></i></a>
</div>
    </div>
      </div>
        <div class="mt-2 flex-shrink-0 align-items-center">
        <div class="sidebar-search">
        <div id="quarto-search" class="" title="Search"></div>
        </div>
        </div>
    <div class="sidebar-menu-container"> 
    <ul class="list-unstyled mt-1">
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../index.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text">Welcome</span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/01-zig-weird.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text"><span class="chapter-number">1</span>&nbsp; <span class="chapter-title">Introducing Zig</span></span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/03-structs.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text"><span class="chapter-number">2</span>&nbsp; <span class="chapter-title">Control flow, structs, modules and types</span></span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/01-memory.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text"><span class="chapter-number">3</span>&nbsp; <span class="chapter-title">Memory and Allocators</span></span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/01-base64.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text"><span class="chapter-number">4</span>&nbsp; <span class="chapter-title">Project 1 - Building a base64 encoder/decoder</span></span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/02-debugging.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text"><span class="chapter-number">5</span>&nbsp; <span class="chapter-title">Debugging Zig applications</span></span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/05-pointers.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text"><span class="chapter-number">6</span>&nbsp; <span class="chapter-title">Pointers and Optionals</span></span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/04-http-server.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text"><span class="chapter-number">7</span>&nbsp; <span class="chapter-title">Project 2 - Building a HTTP Server from scratch</span></span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/03-unittests.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text"><span class="chapter-number">8</span>&nbsp; <span class="chapter-title">Unit tests</span></span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/07-build-system.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text"><span class="chapter-number">9</span>&nbsp; <span class="chapter-title">Build System</span></span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/09-error-handling.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text"><span class="chapter-number">10</span>&nbsp; <span class="chapter-title">Error handling and unions</span></span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/09-data-structures.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text"><span class="chapter-number">11</span>&nbsp; <span class="chapter-title">Data Structures</span></span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/10-stack-project.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text"><span class="chapter-number">12</span>&nbsp; <span class="chapter-title">Project 3 - Building a stack data structure</span></span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/12-file-op.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text"><span class="chapter-number">13</span>&nbsp; <span class="chapter-title">Filesystem and Input/Output (IO)</span></span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/14-zig-c-interop.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text"><span class="chapter-number">14</span>&nbsp; <span class="chapter-title">Zig interoperability with C</span></span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/13-image-filter.html" class="sidebar-item-text sidebar-link active">
 <span class="menu-text"><span class="chapter-number">15</span>&nbsp; <span class="chapter-title">Project 4 - Developing an image filter</span></span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/14-threads.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text"><span class="chapter-number">16</span>&nbsp; <span class="chapter-title">Introducing threads and parallelism in Zig</span></span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/15-vectors.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text"><span class="chapter-number">17</span>&nbsp; <span class="chapter-title">Introducing Vectors and SIMD</span></span></a>
  </div>
</li>
        <li class="sidebar-item">
  <div class="sidebar-item-container"> 
  <a href="../Chapters/references.html" class="sidebar-item-text sidebar-link">
 <span class="menu-text">References</span></a>
  </div>
</li>
    </ul>
    </div>
</nav>
<div id="quarto-sidebar-glass" class="quarto-sidebar-collapse-item" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item"></div>
<!-- margin-sidebar -->
    <div id="quarto-margin-sidebar" class="sidebar margin-sidebar">
        <nav id="TOC" role="doc-toc" class="toc-active">
    <h2 id="toc-title">Table of contents</h2>
   
  <ul>
  <li><a href="#sec-eyes" id="toc-sec-eyes" class="nav-link active" data-scroll-target="#sec-eyes"><span class="header-section-number">15.1</span> How we see things?</a></li>
  <li><a href="#sec-digital-img" id="toc-sec-digital-img" class="nav-link" data-scroll-target="#sec-digital-img"><span class="header-section-number">15.2</span> How digital images work?</a>
  <ul class="collapse">
  <li><a href="#images-are-displayed-from-top-to-bottom" id="toc-images-are-displayed-from-top-to-bottom" class="nav-link" data-scroll-target="#images-are-displayed-from-top-to-bottom"><span class="header-section-number">15.2.1</span> Images are displayed from top to bottom</a></li>
  <li><a href="#sec-pixel-repr" id="toc-sec-pixel-repr" class="nav-link" data-scroll-target="#sec-pixel-repr"><span class="header-section-number">15.2.2</span> Representing the matrix of pixels in code</a></li>
  </ul></li>
  <li><a href="#the-png-library-that-we-are-going-to-use" id="toc-the-png-library-that-we-are-going-to-use" class="nav-link" data-scroll-target="#the-png-library-that-we-are-going-to-use"><span class="header-section-number">15.3</span> The PNG library that we are going to use</a></li>
  <li><a href="#reading-the-png-file" id="toc-reading-the-png-file" class="nav-link" data-scroll-target="#reading-the-png-file"><span class="header-section-number">15.4</span> Reading the PNG file</a>
  <ul class="collapse">
  <li><a href="#reading-the-image-header-section" id="toc-reading-the-image-header-section" class="nav-link" data-scroll-target="#reading-the-image-header-section"><span class="header-section-number">15.4.1</span> Reading the image header section</a></li>
  <li><a href="#allocating-space-for-the-pixel-data" id="toc-allocating-space-for-the-pixel-data" class="nav-link" data-scroll-target="#allocating-space-for-the-pixel-data"><span class="header-section-number">15.4.2</span> Allocating space for the pixel data</a></li>
  <li><a href="#decoding-the-image-data" id="toc-decoding-the-image-data" class="nav-link" data-scroll-target="#decoding-the-image-data"><span class="header-section-number">15.4.3</span> Decoding the image data</a></li>
  <li><a href="#looking-at-the-pixel-data" id="toc-looking-at-the-pixel-data" class="nav-link" data-scroll-target="#looking-at-the-pixel-data"><span class="header-section-number">15.4.4</span> Looking at the pixel data</a></li>
  </ul></li>
  <li><a href="#applying-the-image-filter" id="toc-applying-the-image-filter" class="nav-link" data-scroll-target="#applying-the-image-filter"><span class="header-section-number">15.5</span> Applying the image filter</a></li>
  <li><a href="#saving-the-grayscale-version-of-the-image" id="toc-saving-the-grayscale-version-of-the-image" class="nav-link" data-scroll-target="#saving-the-grayscale-version-of-the-image"><span class="header-section-number">15.6</span> Saving the grayscale version of the image</a></li>
  <li><a href="#building-our-project" id="toc-building-our-project" class="nav-link" data-scroll-target="#building-our-project"><span class="header-section-number">15.7</span> Building our project</a></li>
  </ul>
</nav>
    </div>
<!-- main -->
<main class="content" id="quarto-document-content">


<header id="title-block-header" class="quarto-title-block default">
<div class="quarto-title">
<h1 class="title"><span class="chapter-number">15</span>&nbsp; <span class="chapter-title">Project 4 - Developing an image filter</span></h1>
</div>



<div class="quarto-title-meta">

    
  
    
  </div>
  


</header>


<p>In this chapter we are going to build a new project. The objective of this project is to write a program that applies a filter over an image. More specifically, a “grayscale filter”, which transforms any color image into a grayscale image.</p>
<p>We are going to use the image displayed in <a href="#fig-pascal" class="quarto-xref">Figure&nbsp;<span>15.1</span></a> in this project. In other words, we want to transform this colored image into a grayscale image, by using our “image filter program” written in Zig.</p>
<div id="fig-pascal" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-pascal-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="../ZigExamples/image_filter/pedro_pascal.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-pascal-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;15.1: A photo of the chilean-american actor Pedro Pascal. Source: Google Images.
</figcaption>
</figure>
</div>
<p>We don’t need to write a lot of code to build such “image filter program”. However, we first need to understand how digital images work. That is why we begin this chapter by explaining the theory behind digital images and how colors are represented in modern computers. We also give a brief explanation about the PNG (Portable Network Graphics) file format, which is the format used in the example images.</p>
<p>At the end of this chapter, we should have a full example of a program that takes the PNG image displayed in <a href="#fig-pascal" class="quarto-xref">Figure&nbsp;<span>15.1</span></a> as input, and writes a new image to the current working directory that is the grayscale version of this input image. This grayscale version of <a href="#fig-pascal" class="quarto-xref">Figure&nbsp;<span>15.1</span></a> is exposed in <a href="#fig-pascal-gray" class="quarto-xref">Figure&nbsp;<span>15.2</span></a>. You can find the full source code of this small project at the <code>ZigExamples/image_filter</code> <a href="https://github.com/pedropark99/zig-book/tree/main/ZigExamples/image_filter">folder at the official repository of this book</a><a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>.</p>
<div id="fig-pascal-gray" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-pascal-gray-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="../ZigExamples/image_filter/pedro_pascal_filter.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-pascal-gray-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;15.2: The grayscale version of the photo.
</figcaption>
</figure>
</div>
<section id="sec-eyes" class="level2" data-number="15.1">
<h2 data-number="15.1" class="anchored" data-anchor-id="sec-eyes"><span class="header-section-number">15.1</span> How we see things?</h2>
<p>In this section, I want to briefly describe to you how we (humans) actually see things with our own eyes. I mean, how our eyes work? If you do have a very basic understanding of how our eyes work, you will understand more easily how digital images are made. Because the techniques behind digital images were developed by taking a lot of inspiration from how our human eyes work.</p>
<p>You can interpret a human eye as a light sensor, or, a light receptor. The eye receives some amount of light as input, and it interprets the colors that are present in this “amount of light”. If no amount of light hits the eye, then, the eye cannot extract color from it, and as result, we end up seeing nothing, or, more precisely, we see complete blackness.</p>
<p>Therefore, everything depends on light. What we actually see are the colors (blue, red, orange, green, purple, yellow, etc.) that are being reflected from the light that is hitting our eyes. <strong>Light is the source of all colors!</strong> This is what Isaac Newton discovered on his famous prism experiment<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a> in the 1660s.</p>
<p>Inside our eyes, we have a specific type of cell called the “cone cell”. Our eye have three different types, or, three different versions of these “cone cells”. Each type of cone cell is very sensitive to a specific spectrum of the light. More specifically, to the spectrums that define the colors red, green and blue. So, in summary, our eyes have specific types of cells that are highly sensitive to these three colors (red, green and blue).</p>
<p>These are the cells responsible for perceiving the color present in the light that hits our eyes. As a result, our eyes perceives color as a mixture of these three colors (red, green and blue). By having an amount of each one of these three colors, and mixing them together, we can get any other visible color that we want. So every color that we see is perceived as a specific mixture of blues, greens and reds, like 30% of red, plus 20% of green, plus 50% of blue.</p>
<p>When these cone cells perceive (or, detect) the colors that are found in the light that is hitting our eyes, these cells produce electrical signals, which are sent to the brain. Our brain interprets these electrical signals, and use them to form the image that we are seeing inside our head.</p>
<p>Based on what we have discussed here, the bullet points exposed below describes the sequence of events that composes this very simplified version of how our human eyes work:</p>
<ol type="1">
<li>Light hits our eyes.</li>
<li>The cone cells perceive the colors that are present in this light.</li>
<li>Cone cells produce electrical signals that describes the colors that were perceived in the light.</li>
<li>The electrical signals are sent to the brain.</li>
<li>Brain interprets these signals, and form the image based on the colors identified by these electrical signals.</li>
</ol>
</section>
<section id="sec-digital-img" class="level2" data-number="15.2">
<h2 data-number="15.2" class="anchored" data-anchor-id="sec-digital-img"><span class="header-section-number">15.2</span> How digital images work?</h2>
<p>A digital image is a “digital representation” of an image that we see with our eyes. In other words, a digital image is a “digital representation” of the colors that we see and perceive through the light. In the digital world, we have two types of images, which are: vector images and raster images. Vector images are not described here. So just remember that the content discussed here <strong>is related solely to raster images</strong>, and not vector images.</p>
<p>A raster image is a type of digital image that is represented as a 2D (two dimensional) matrix of pixels. In other words, every raster image is basically a rectangle of pixels, and each pixel have a particular color. So, a raster image is just a rectangle of pixels, and each of these pixels are displayed in the screen of your computer (or the screen of any other device, e.g.&nbsp;laptop, tablet, smartphone, etc.) as a color.</p>
<p><a href="#fig-raster" class="quarto-xref">Figure&nbsp;<span>15.3</span></a> demonstrates this idea. If you take any raster image, and you zoom into it very hard, you will see the actual pixels of the image. JPEG, TIFF and PNG are file formats that are commonly used to store raster images.</p>
<div id="fig-raster" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-raster-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="../Figures/imagem-raster.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-raster-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;15.3: Zooming over a raster image to see the pixels. Source: Google Images.
</figcaption>
</figure>
</div>
<p>The more pixels the image has, the more information and detail we can include in the image. The more accurate, sharp and pretty the image will look. This is why photographic cameras usually produce big raster images, with several megapixels of resolution, to include as much detail as possible into the final image. As an example, a digital image with dimensions of 1920 pixels wide and 1080 pixels high, would be a image that contains <span class="math inline">\(1920 \times 1080 = 2073600\)</span> pixels in total. You could also say that the “total area” of the image is of 2073600 pixels, although the concept of “area” is not really used here in computer graphics.</p>
<p>Most digital images we see in our modern world uses the RGB color model. RGB stands for (red, green and blue). So the color of each pixel in these raster images are usually represented as a mixture of red, green and blue, just like in our eyes. That is, the color of each pixel is identified by a set of three different integer values. Each integer value identifies the “amount” of each color (red, green and blue). For example, the set <code>(199, 78, 70)</code> identifies a color that is more close to red. We have 199 of red, 78 of green, and 70 of blue. In contrast, the set <code>(129, 77, 250)</code> describes a color that is more close to purple. Et cetera.</p>
<section id="images-are-displayed-from-top-to-bottom" class="level3" data-number="15.2.1">
<h3 data-number="15.2.1" class="anchored" data-anchor-id="images-are-displayed-from-top-to-bottom"><span class="header-section-number">15.2.1</span> Images are displayed from top to bottom</h3>
<p>This is not a rule written in stone, but the big majority of digital images are displayed from top to bottom and left to right. Most computers screens also follow this pattern. So, the first pixels in the image are the ones that are at the top and left corner of the image. You can find a visual representation of this logic in <a href="#fig-img-display" class="quarto-xref">Figure&nbsp;<span>15.4</span></a>.</p>
<p>Also notice in <a href="#fig-img-display" class="quarto-xref">Figure&nbsp;<span>15.4</span></a> that, because a raster image is essentially a 2D matrix of pixels, the image is organized into rows and columns of pixels. The columns are defined by the horizontal x axis, while the rows are defined by the vertical y axis.</p>
<p>Each pixel (i.e., the gray rectangles) exposed in <a href="#fig-img-display" class="quarto-xref">Figure&nbsp;<span>15.4</span></a> contains a number inside of it. These numbers are the indexes of the pixels. You can notice that the first pixels are in the top and left corner, and also, that the indexes of these pixels “grow to the sides”, or, in other words, they grow in the direction of the horizontal x axis. Most raster images are organized as rows of pixels. Thus, when these digital images are displayed, the screen display the first row of pixels, then, the second row, then, the third row, etc.</p>
<div id="fig-img-display" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-img-display-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="./../Figures/image-display.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-img-display-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;15.4: How the pixels of raster images are displayed.
</figcaption>
</figure>
</div>
</section>
<section id="sec-pixel-repr" class="level3" data-number="15.2.2">
<h3 data-number="15.2.2" class="anchored" data-anchor-id="sec-pixel-repr"><span class="header-section-number">15.2.2</span> Representing the matrix of pixels in code</h3>
<p>Ok, we know already that raster images are represented as 2D matrices of pixels. But we do not have a notion of a 2D matrix in Zig. Actually, most low-level languages in general (Zig, C, Rust, etc.) do not have such notion. So how can we represent such matrix of pixels in Zig, or any other low-level language? The strategy that most programmers choose in this situation is to just use a normal 1D array to store the values of this 2D matrix. In other words, you just create an normal 1D array, and store all values from both dimensions into this 1D array.</p>
<p>As an example, suppose we have a very small image of dimensions 4x3. Since a raster image is represented as a 2D matrix of pixels, and each pixel is represented by 3 “unsigned 8-bit” integer values, we have 12 pixels in total in this image, which are represented by <span class="math inline">\(3 \times 12 = 36\)</span> integer values. Therefore, we need to create an array of 36 <code>u8</code> values to store this small image.</p>
<p>The reason why unsigned 8-bit integer (<code>u8</code>) values are used to represent the amounts of each color, instead of any other integer type, is because they take the minimum amount of space as possible, or, the minimum amount of bits as possible. Which helps to reduces the binary size of the image, i.e., the 2D matrix. Also, they convey a good amount of precision and detail about the colors, even though they can represent a relatively small range (from 0 to 255) of “color amounts”.</p>
<p>Coming back to our initial example of a 4x3 image, the <code>matrix</code> object exposed below could be an example of an 1D array that stores the data that represents this 4x3 image.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb1"><pre class="sourceCode zig code-with-copy"><code class="sourceCode zig"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> matrix = [_]<span class="dt">u8</span><span class="op">{</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>    <span class="dv">201</span>, <span class="dv">10</span>, <span class="dv">25</span>, <span class="dv">185</span>, <span class="dv">65</span>, <span class="dv">70</span>,</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>    <span class="dv">65</span>, <span class="dv">120</span>, <span class="dv">110</span>, <span class="dv">65</span>, <span class="dv">120</span>, <span class="dv">117</span>,</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>    <span class="dv">98</span>, <span class="dv">95</span>, <span class="dv">12</span>, <span class="dv">213</span>, <span class="dv">26</span>, <span class="dv">88</span>,</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>    <span class="dv">143</span>, <span class="dv">112</span>, <span class="dv">65</span>, <span class="dv">97</span>, <span class="dv">99</span>, <span class="dv">205</span>,</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>    <span class="dv">234</span>, <span class="dv">105</span>, <span class="dv">56</span>, <span class="dv">43</span>, <span class="dv">44</span>, <span class="dv">216</span>,</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>    <span class="dv">45</span>, <span class="dv">59</span>, <span class="dv">243</span>, <span class="dv">211</span>, <span class="dv">209</span>, <span class="dv">54</span>,</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="op">}</span>;</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</div>
<p>The first three integer values in this array are the color amounts of the first pixel in the image. The next three integers are the colors amounts for the second pixel. And the sequence goes on in this pattern. Having that in mind, the size of the array that stores a raster image is usually a multiple of 3. In this case, the array have a size of 36.</p>
<p>I mean, the size of the array is <strong>usually</strong> a multiple of 3, because in specific circumstances, it can also be a multiple of 4. This happens when a transparency amount is also included into the raster image. In other words, there are some types of raster images that uses a different color model, which is the RGBA (red, green, blue and alpha) color model. The “alpha” corresponds to an amount of transparency in the pixel. So every pixel in a RGBA image is represented by a red, green, blue and alpha values.</p>
<p>Most raster images uses the standard RGB model, so, for the most part, you will see arrays sizes that are multiples of 3. But some images, especially the ones that are stored in PNG files, might be using the RGBA model, and, therefore, are represented by an array whose size is a multiple of 4.</p>
<p>In our case here, the example image of our project (<a href="#fig-pascal" class="quarto-xref">Figure&nbsp;<span>15.1</span></a>) is a raster image stored in a PNG file, and this specific image is using the RGBA color model. Therefore, each pixel in the image is represented by 4 different integer values, and, as consequence, to store this image in our Zig code, we need to create an array whose size is a multiple of 4.</p>
</section>
</section>
<section id="the-png-library-that-we-are-going-to-use" class="level2" data-number="15.3">
<h2 data-number="15.3" class="anchored" data-anchor-id="the-png-library-that-we-are-going-to-use"><span class="header-section-number">15.3</span> The PNG library that we are going to use</h2>
<p>Let’s begin our project by focusing on writing the necessary Zig code to read the data from the PNG file. In other words, we want to read the PNG file exposed in <a href="#fig-pascal" class="quarto-xref">Figure&nbsp;<span>15.1</span></a>, and parse its data to extract the 2D matrix of pixels that represents the image.</p>
<p>As we have discussed in <a href="#sec-pixel-repr" class="quarto-xref"><span>Section 15.2.2</span></a>, the image that we are using as example here is a PNG file that uses the RGBA color model, and, therefore, each pixel of the image is represented by 4 integer values. You can download this image by visiting the <code>ZigExamples/image_filter</code> <a href="https://github.com/pedropark99/zig-book/tree/main/ZigExamples/image_filter">folder at the official repository of this book</a><a href="#fn3" class="footnote-ref" id="fnref3" role="doc-noteref"><sup>3</sup></a>. You can also find in this folder the complete source code of this small project that we are developing here.</p>
<p>There are some C libraries available that we can use to read and parse PNG files. The most famous and used of all is <code>libpng</code>, which is the “official library” for reading and writing PNG files. Although this library is available on most operating system, it’s well known for being complex and hard to use.</p>
<p>That is why, I’m going to use a more modern alternative here in this project, which is the <code>libspng</code> library. I choose to use this C library here, because it’s much, much simpler to use than <code>libpng</code>, and it also offers very good performance for all operations. You can checkout the <a href="https://libspng.org/">official website of the library</a><a href="#fn4" class="footnote-ref" id="fnref4" role="doc-noteref"><sup>4</sup></a> to know more about it. You will also find there some documentation that might help you to understand and follow the code examples exposed here.</p>
<p>First of all, remember to build and install this <code>libspng</code> into your system. Because if you don’t do this step, the <code>zig</code> compiler will not be able to find the files and resources of this library in your computer, and link them with the Zig source code that we are writing together here. There is good information about how to build and install the library at the <a href="https://libspng.org/docs/build/">build section of the library documentation</a><a href="#fn5" class="footnote-ref" id="fnref5" role="doc-noteref"><sup>5</sup></a>.</p>
</section>
<section id="reading-the-png-file" class="level2" data-number="15.4">
<h2 data-number="15.4" class="anchored" data-anchor-id="reading-the-png-file"><span class="header-section-number">15.4</span> Reading the PNG file</h2>
<p>In order to extract the pixel data from the PNG file, we need to read and decode the file. A PNG file is just a binary file written in the “PNG format”. Luckily, the <code>libspng</code> library offers a function called <code>spng_decode_image()</code> that does all this heavy work for us.</p>
<p>Now, since <code>libspng</code> is a C library, most of the file and I/O operations in this library are made by using a <code>FILE</code> C pointer. Because of that, is probably a better idea to use the <code>fopen()</code> C function to open our PNG file, instead of using the <code>openFile()</code> method that I introduced in <a href="12-file-op.html" class="quarto-xref"><span>Chapter 13</span></a>. That is why I’m importing the <code>stdio.h</code> C header in this project, and using the <code>fopen()</code> C function to open the file.</p>
<p>If you look at the snippet below, you can see that we are:</p>
<ol type="1">
<li>opening the PNG file with <code>fopen()</code>.</li>
<li>creating the <code>libspng</code> context with <code>spng_ctx_new()</code>.</li>
<li>using <code>spng_set_png_file()</code> to specify the <code>FILE</code> object that reads the PNG file that we are going to use.</li>
</ol>
<p>Every operation in <code>libspng</code> is made through a “context object”. In our snippet below, this object is <code>ctx</code>. Also, to perform an operation over a PNG file, we need to specify which exact PNG file we are referring to. This is the job of <code>spng_set_png_file()</code>. We are using this function to specify the file descriptor object that reads the PNG file that we want to use.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb2"><pre class="sourceCode zig code-with-copy"><code class="sourceCode zig"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> c = <span class="bu">@cImport</span>(<span class="op">{</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>    <span class="bu">@cDefine</span>(<span class="st">"_NO_CRT_STDIO_INLINE"</span>, <span class="st">"1"</span>);</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>    <span class="bu">@cInclude</span>(<span class="st">"stdio.h"</span>);</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>    <span class="bu">@cInclude</span>(<span class="st">"spng.h"</span>);</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="op">}</span>);</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> path = <span class="st">"pedro_pascal.png"</span>;</span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> file_descriptor = c.fopen(path, <span class="st">"rb"</span>);</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="kw">if</span> (file_descriptor == <span class="cn">null</span>) <span class="op">{</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>    <span class="bu">@panic</span>(<span class="st">"Could not open file!"</span>);</span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> ctx = c.spng_ctx_new(<span class="dv">0</span>) <span class="kw">orelse</span> <span class="kw">unreachable</span>;</span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a>_ = c.spng_set_png_file(</span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a>    ctx, <span class="bu">@ptrCast</span>(file_descriptor)</span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a>);</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</div>
<p>Before we continue, is important to emphasize the following: since we have opened the file with <code>fopen()</code>, we have to remember to close the file at the end of the program, with <code>fclose()</code>. In other words, after we have done everything that we wanted to do with the PNG file <code>pedro_pascal.png</code>, we need to close this file, by applying <code>fclose()</code> over the file descriptor object. We could use also the <code>defer</code> keyword to help us in this task, if we want to. This code snippet below demonstrates this step:</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb3"><pre class="sourceCode zig code-with-copy"><code class="sourceCode zig"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">if</span> (c.fclose(file_descriptor) != <span class="dv">0</span>) <span class="op">{</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">return</span> <span class="kw">error</span>.CouldNotCloseFileDescriptor;</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</div>
<section id="reading-the-image-header-section" class="level3" data-number="15.4.1">
<h3 data-number="15.4.1" class="anchored" data-anchor-id="reading-the-image-header-section"><span class="header-section-number">15.4.1</span> Reading the image header section</h3>
<p>Now, the context object <code>ctx</code> is aware of our PNG file <code>pedro_pascal.png</code>, because it has access to a file descriptor object to this file. The first thing that we are going to do is to read the “image header section” of the PNG file. This “image header section” is the section of the file that contains some basic information about the PNG file, like, the bit depth of the pixel data of the image, the color model used in the file, the dimensions of the image (height and width in number of pixels), etc.</p>
<p>To make things easier, I will encapsulate this “read image header” operation into a nice and small function called <code>get_image_header()</code>. All that this function needs to do is to call the <code>spng_get_ihdr()</code> function. This function from <code>libspng</code> is responsible for reading the image header data, and storing it into a C struct named <code>spng_ihdr</code>. Thus, an object of type <code>spng_ihdr</code> is a C struct that contains the data from the image header section of the PNG file.</p>
<p>Since this Zig function is receiving a C object (the <code>libspng</code> context object) as input, I marked the function argument <code>ctx</code> as “a pointer to the context object” (<code>*c.spng_ctx</code>), following the recommendations that we have discussed in <a href="14-zig-c-interop.html#sec-pass-c-structs" class="quarto-xref"><span>Section 14.5</span></a>.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb4"><pre class="sourceCode zig code-with-copy"><code class="sourceCode zig"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> get_image_header(ctx: *c.spng_ctx) !c.spng_ihdr <span class="op">{</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">var</span> image_header: c.spng_ihdr = <span class="cn">undefined</span>;</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>    <span class="kw">if</span> (c.spng_get_ihdr(ctx, &amp;image_header) != <span class="dv">0</span>) <span class="op">{</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>        <span class="kw">return</span> <span class="kw">error</span>.CouldNotGetImageHeader;</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>    <span class="kw">return</span> image_header;</span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a><span class="kw">var</span> image_header = <span class="kw">try</span> get_image_header(ctx);</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</div>
<p>Also notice in this function, that I’m checking if the <code>spng_get_ihdr()</code> function call have returned or not an integer value that is different than zero. Most functions from the <code>libspng</code> library return a code status as result, and the code status “zero” means “success”. So any code status that is different than zero means that an error occurred while running <code>spng_get_ihdr()</code>. This is why I’m returning an error value from the function in case the code status returned by the function is different than zero.</p>
</section>
<section id="allocating-space-for-the-pixel-data" class="level3" data-number="15.4.2">
<h3 data-number="15.4.2" class="anchored" data-anchor-id="allocating-space-for-the-pixel-data"><span class="header-section-number">15.4.2</span> Allocating space for the pixel data</h3>
<p>Before we read the pixel data from the PNG file, we need to allocate enough space to hold this data. But in order to allocate such space, we first need to know how much space we need to allocate. The dimensions of the image are obviously needed to calculate the size of this space. But there are other elements that also affect this number, such as the color model used in the image, the bit depth, and others.</p>
<p>Anyway, all of this means that calculating the size of the space that we need, is not a simple task. That is why the <code>libspng</code> library offers an utility function named <code>spng_decoded_image_size()</code> to calculate this size for us. Once again, I’m going to encapsulate the logic around this C function into a nice and small Zig function named <code>calc_output_size()</code>. You can see below that this function returns a nice integer value as result, informing the size of the space that we need to allocate.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb5"><pre class="sourceCode zig code-with-copy"><code class="sourceCode zig"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> calc_output_size(ctx: *c.spng_ctx) !<span class="dt">u64</span> <span class="op">{</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">var</span> output_size: <span class="dt">u64</span> = <span class="dv">0</span>;</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>    <span class="kw">const</span> status = c.spng_decoded_image_size(</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>        ctx, c.SPNG_FMT_RGBA8, &amp;output_size</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>    );</span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>    <span class="kw">if</span> (status != <span class="dv">0</span>) <span class="op">{</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>        <span class="kw">return</span> <span class="kw">error</span>.CouldNotCalcOutputSize;</span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>    <span class="kw">return</span> output_size;</span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</div>
<p>You might quest yourself what the value <code>SPNG_FMT_RGBA8</code> means. This value is actually an enum value defined in the <code>spng.h</code> C header file. This enum is used to identify a “PNG format”. More precisely, it identifies a PNG file that uses the RGBA color model and 8 bit depth. So, by providing this enum value as input to the <code>spng_decoded_image_size()</code> function, we are saying to this function to calculate the size of the decoded pixel data, by considering a PNG file that follows this “RGBA color model with 8 bit depth” format.</p>
<p>Having this function, we can use it in conjunction with an allocator object, to allocate an array of bytes (<code>u8</code> values) that is big enough to store the decoded pixel data of the image. Notice that I’m using <code>@memset()</code> to initialize the entire array to zero.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb6"><pre class="sourceCode zig code-with-copy"><code class="sourceCode zig"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> output_size = <span class="kw">try</span> calc_output_size(ctx);</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="kw">var</span> buffer = <span class="kw">try</span> allocator.alloc(<span class="dt">u8</span>, output_size);</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="bu">@memset</span>(buffer[<span class="dv">0</span>..], <span class="dv">0</span>);</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</div>
</section>
<section id="decoding-the-image-data" class="level3" data-number="15.4.3">
<h3 data-number="15.4.3" class="anchored" data-anchor-id="decoding-the-image-data"><span class="header-section-number">15.4.3</span> Decoding the image data</h3>
<p>Now that we have the necessary space to store the decoded pixel data of the image, we can start to actually decode and extract this pixel data from the image, by using the <code>spng_decode_image()</code> C function.</p>
<p>The <code>read_data_to_buffer()</code> Zig function exposed below summarises the necessary steps to read this decoded pixel data, and store it into an input buffer. Notice that this function is encapsulating the logic around the <code>spng_decode_image()</code> function. Also, we are using the <code>SPNG_FMT_RGBA8</code> enum value once again to inform the corresponding function, that the PNG image being decoded, uses the RGBA color model and 8 bit depth.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb7"><pre class="sourceCode zig code-with-copy"><code class="sourceCode zig"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> read_data_to_buffer(ctx: *c.spng_ctx, buffer: []<span class="dt">u8</span>) !<span class="dt">void</span> <span class="op">{</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">const</span> status = c.spng_decode_image(</span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a>        ctx,</span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a>        buffer.ptr,</span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a>        buffer.len,</span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a>        c.SPNG_FMT_RGBA8,</span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a>        <span class="dv">0</span></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a>    );</span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a>    <span class="kw">if</span> (status != <span class="dv">0</span>) <span class="op">{</span></span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a>        <span class="kw">return</span> <span class="kw">error</span>.CouldNotDecodeImage;</span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</div>
<p>Having this function at hand, we can apply it over our context object, and also, over the buffer object that we have allocated in the previous section to hold the decoded pixel data of the image:</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb8"><pre class="sourceCode zig code-with-copy"><code class="sourceCode zig"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="kw">try</span> read_data_to_buffer(ctx, buffer[<span class="dv">0</span>..]);</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</div>
</section>
<section id="looking-at-the-pixel-data" class="level3" data-number="15.4.4">
<h3 data-number="15.4.4" class="anchored" data-anchor-id="looking-at-the-pixel-data"><span class="header-section-number">15.4.4</span> Looking at the pixel data</h3>
<p>Now that we have the pixel data stored in our “buffer object”, we can take a quick look at the bytes. In the example below, we are looking at the first 12 bytes in the decoded pixel data.</p>
<p>If you take a close look at these values, you might notice that every 4 bytes in the sequence is 255. Which, coincidentally is the maximum possible integer value to be represented by a <code>u8</code> value. So, if the range from 0 to 255, which is the range of integer values that can be represented by an <code>u8</code> value, can be represented as a scale from 0% to 100%, these 255 values are essentially 100% in that scale.</p>
<p>If you recall from <a href="#sec-pixel-repr" class="quarto-xref"><span>Section 15.2.2</span></a>, I have described in that section that our <code>pedro_pascal.png</code> PNG file uses the RGBA color model, which adds an alpha (or transparency) byte to each pixel in the image. As consequence, each pixel in the image is represented by 4 bytes. Since we are looking here are the first 12 bytes in the image, it means that we are looking at the data from the first <span class="math inline">\(12 / 4 = 3\)</span> pixels in the image.</p>
<p>So, based on how these first 12 bytes (or these 3 pixels) look, with these 255 values at every 4 bytes, we can say that is likely that every pixel in the image have alpha (or transparency) setted to 100%. This might not be true, but, is the most likely possibility. Also, if we look at the image itself, which if your recall is exposed in <a href="#fig-pascal" class="quarto-xref">Figure&nbsp;<span>15.1</span></a>, we can see that the transparency does not change across the image, which enforces this theory.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb9"><pre class="sourceCode zig code-with-copy"><code class="sourceCode zig"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="kw">try</span> stdout.print(<span class="st">"{any}</span><span class="sc">\n</span><span class="st">"</span>, .<span class="op">{</span>buffer[<span class="dv">0</span>..<span class="dv">12</span>]<span class="op">}</span>);</span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a><span class="kw">try</span> stdout.flush();</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</div>
<pre><code>{
    200, 194, 216, 255, 203, 197,
    219, 255, 206, 200, 223, 255
}</code></pre>
<p>We can see in the above result that the first pixel in this image have 200 of red, 194 of green, and 216 of blue. How do I know the order in which the colors appears in the sequence? If you have not guessed that yet, is because of the acronym RGB. First RED, then GREEN, then BLUE. If we scale these integer values according to our scale of 0% to 100% (0 to 255), we get 78% of red, 76% of green and 85% of blue.</p>
</section>
</section>
<section id="applying-the-image-filter" class="level2" data-number="15.5">
<h2 data-number="15.5" class="anchored" data-anchor-id="applying-the-image-filter"><span class="header-section-number">15.5</span> Applying the image filter</h2>
<p>Now that we have the data of each pixel in the image, we can focus on applying our image filter over these pixels. Remember, our objective here is to apply a grayscale filter over the image. A grayscale filter is a filter that transforms a colored image into a grayscale image.</p>
<p>There are different formulas and strategies to transform a colored image into a grayscale image. But all of these different strategies normally involve applying some math over the colors of each pixel. In this project, we are going to use the most general formula, which is exposed below. This formula considers <span class="math inline">\(r\)</span> as the red of the pixel, <span class="math inline">\(g\)</span> as the green, <span class="math inline">\(b\)</span> as the blue, and <span class="math inline">\(p'\)</span> as the linear luminance of the pixel.</p>
<p><span id="eq-grayscale"><span class="math display">\[
    p' = (0.2126 \times r) + (0.7152 \times g) + (0.0722 \times b)
\tag{15.1}\]</span></span></p>
<p>This <a href="#eq-grayscale" class="quarto-xref">Equation&nbsp;<span>15.1</span></a> is the formula to calculate the linear luminance of a pixel. It’s worth noting that this formula works only for images whose pixels are using the sRGB color space, which is the standard color space for the web. Thus, ideally, all images on the web should use this color space. Luckily, this is our case here, i.e., the <code>pedro_pascal.png</code> image is using this sRGB color space, and, as consequence, we can use the <a href="#eq-grayscale" class="quarto-xref">Equation&nbsp;<span>15.1</span></a>. You can read more about this formula at the Wikipedia page for grayscale <span class="citation" data-cites="wiki_grayscale">(<a href="references.html#ref-wiki_grayscale" role="doc-biblioref">Wikipedia 2024</a>)</span>.</p>
<p>The <code>apply_image_filter()</code> function exposed below summarises the necessary steps to apply <a href="#eq-grayscale" class="quarto-xref">Equation&nbsp;<span>15.1</span></a> over the pixels in the image. We just apply this function over our buffer object that contains our pixel data, and, as result, the pixel data stored in this buffer object should now represent the grayscale version of our image.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb11"><pre class="sourceCode zig code-with-copy"><code class="sourceCode zig"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> apply_image_filter(buffer:[]<span class="dt">u8</span>) !<span class="dt">void</span> <span class="op">{</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">const</span> len = buffer.len;</span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a>    <span class="kw">const</span> red_factor: <span class="dt">f16</span> = <span class="fl">0.2126</span>;</span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a>    <span class="kw">const</span> green_factor: <span class="dt">f16</span> = <span class="fl">0.7152</span>;</span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a>    <span class="kw">const</span> blue_factor: <span class="dt">f16</span> = <span class="fl">0.0722</span>;</span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a>    <span class="kw">var</span> index: <span class="dt">u64</span> = <span class="dv">0</span>;</span>
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a>    <span class="kw">while</span> (index &lt; len) : (index += <span class="dv">4</span>) <span class="op">{</span></span>
<span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a>        <span class="kw">const</span> rf: <span class="dt">f16</span> = <span class="bu">@floatFromInt</span>(buffer[index]);</span>
<span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a>        <span class="kw">const</span> gf: <span class="dt">f16</span> = <span class="bu">@floatFromInt</span>(buffer[index + <span class="dv">1</span>]);</span>
<span id="cb11-10"><a href="#cb11-10" aria-hidden="true" tabindex="-1"></a>        <span class="kw">const</span> bf: <span class="dt">f16</span> = <span class="bu">@floatFromInt</span>(buffer[index + <span class="dv">2</span>]);</span>
<span id="cb11-11"><a href="#cb11-11" aria-hidden="true" tabindex="-1"></a>        <span class="kw">const</span> y_linear: <span class="dt">f16</span> = (</span>
<span id="cb11-12"><a href="#cb11-12" aria-hidden="true" tabindex="-1"></a>            (rf * red_factor) + (gf * green_factor)</span>
<span id="cb11-13"><a href="#cb11-13" aria-hidden="true" tabindex="-1"></a>            + (bf * blue_factor)</span>
<span id="cb11-14"><a href="#cb11-14" aria-hidden="true" tabindex="-1"></a>        );</span>
<span id="cb11-15"><a href="#cb11-15" aria-hidden="true" tabindex="-1"></a>        buffer[index] = <span class="bu">@intFromFloat</span>(y_linear);</span>
<span id="cb11-16"><a href="#cb11-16" aria-hidden="true" tabindex="-1"></a>        buffer[index + <span class="dv">1</span>] = <span class="bu">@intFromFloat</span>(y_linear);</span>
<span id="cb11-17"><a href="#cb11-17" aria-hidden="true" tabindex="-1"></a>        buffer[index + <span class="dv">2</span>] = <span class="bu">@intFromFloat</span>(y_linear);</span>
<span id="cb11-18"><a href="#cb11-18" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb11-19"><a href="#cb11-19" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb11-20"><a href="#cb11-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-21"><a href="#cb11-21" aria-hidden="true" tabindex="-1"></a><span class="kw">try</span> apply_image_filter(buffer[<span class="dv">0</span>..]);</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</div>
</section>
<section id="saving-the-grayscale-version-of-the-image" class="level2" data-number="15.6">
<h2 data-number="15.6" class="anchored" data-anchor-id="saving-the-grayscale-version-of-the-image"><span class="header-section-number">15.6</span> Saving the grayscale version of the image</h2>
<p>Since we have now the grayscale version of our image stored in our buffer object, we need to encode this buffer object back into the “PNG format”, and save the encoded data into a new PNG file in our filesystem, so that we can access and see the grayscale version of our image that was produced by our small program.</p>
<p>To do that, the <code>libspng</code> library help us once again by offering an “encode data to PNG” type of function, which is the <code>spng_encode_image()</code> function. But in order to “encode data to PNG” with <code>libspng</code>, we need to create a new context object. This new context object must use an “encoder context”, which is identified by the enum value <code>SPNG_CTX_ENCODER</code>.</p>
<p>The <code>save_png()</code> function exposed below, summarises all the necessary steps to save the grayscale version of our image into a new PNG file in the filesystem. By default, this function will save the grayscale image into a file named <code>pedro_pascal_filter.png</code> in the CWD.</p>
<p>Notice in this code example that we are using the same image header object (<code>image_header</code>) that we have collected previously with the <code>get_image_header()</code> function. Remember, this image header object is a C struct (<code>spng_ihdr</code>) that contains basic information about our PNG file, such as the dimensions of the image, the color model used, etc.</p>
<p>If we wanted to save a very different image in this new PNG file, e.g.&nbsp;an image with different dimensions, or, an image that uses a different color model, a different bit depth, etc. we would have to create a new image header (<code>spng_ihdr</code>) object that describes the properties of this new image.</p>
<p>But we are essentially saving the same image that we have begin with here (the dimensions of the image, the color model, etc. are all still the same). The only difference between the two images are the colors of the pixels, which are now “shades of gray”. As consequence, we can safely use the exact same image header data in this new PNG file.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb12"><pre class="sourceCode zig code-with-copy"><code class="sourceCode zig"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> save_png(image_header: *c.spng_ihdr, buffer: []<span class="dt">u8</span>) !<span class="dt">void</span> <span class="op">{</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">const</span> path = <span class="st">"pedro_pascal_filter.png"</span>;</span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a>    <span class="kw">const</span> file_descriptor = c.fopen(path.ptr, <span class="st">"wb"</span>);</span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a>    <span class="kw">if</span> (file_descriptor == <span class="cn">null</span>) <span class="op">{</span></span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a>        <span class="kw">return</span> <span class="kw">error</span>.CouldNotOpenFile;</span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a>    <span class="kw">const</span> ctx = (</span>
<span id="cb12-8"><a href="#cb12-8" aria-hidden="true" tabindex="-1"></a>        c.spng_ctx_new(c.SPNG_CTX_ENCODER)</span>
<span id="cb12-9"><a href="#cb12-9" aria-hidden="true" tabindex="-1"></a>        <span class="kw">orelse</span> <span class="kw">unreachable</span></span>
<span id="cb12-10"><a href="#cb12-10" aria-hidden="true" tabindex="-1"></a>    );</span>
<span id="cb12-11"><a href="#cb12-11" aria-hidden="true" tabindex="-1"></a>    <span class="kw">defer</span> c.spng_ctx_free(ctx);</span>
<span id="cb12-12"><a href="#cb12-12" aria-hidden="true" tabindex="-1"></a>    _ = c.spng_set_png_file(ctx, <span class="bu">@ptrCast</span>(file_descriptor));</span>
<span id="cb12-13"><a href="#cb12-13" aria-hidden="true" tabindex="-1"></a>    _ = c.spng_set_ihdr(ctx, image_header);</span>
<span id="cb12-14"><a href="#cb12-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-15"><a href="#cb12-15" aria-hidden="true" tabindex="-1"></a>    <span class="kw">const</span> encode_status = c.spng_encode_image(</span>
<span id="cb12-16"><a href="#cb12-16" aria-hidden="true" tabindex="-1"></a>        ctx,</span>
<span id="cb12-17"><a href="#cb12-17" aria-hidden="true" tabindex="-1"></a>        buffer.ptr,</span>
<span id="cb12-18"><a href="#cb12-18" aria-hidden="true" tabindex="-1"></a>        buffer.len,</span>
<span id="cb12-19"><a href="#cb12-19" aria-hidden="true" tabindex="-1"></a>        c.SPNG_FMT_PNG,</span>
<span id="cb12-20"><a href="#cb12-20" aria-hidden="true" tabindex="-1"></a>        c.SPNG_ENCODE_FINALIZE</span>
<span id="cb12-21"><a href="#cb12-21" aria-hidden="true" tabindex="-1"></a>    );</span>
<span id="cb12-22"><a href="#cb12-22" aria-hidden="true" tabindex="-1"></a>    <span class="kw">if</span> (encode_status != <span class="dv">0</span>) <span class="op">{</span></span>
<span id="cb12-23"><a href="#cb12-23" aria-hidden="true" tabindex="-1"></a>        <span class="kw">return</span> <span class="kw">error</span>.CouldNotEncodeImage;</span>
<span id="cb12-24"><a href="#cb12-24" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb12-25"><a href="#cb12-25" aria-hidden="true" tabindex="-1"></a>    <span class="kw">if</span> (c.fclose(file_descriptor) != <span class="dv">0</span>) <span class="op">{</span></span>
<span id="cb12-26"><a href="#cb12-26" aria-hidden="true" tabindex="-1"></a>        <span class="kw">return</span> <span class="kw">error</span>.CouldNotCloseFileDescriptor;</span>
<span id="cb12-27"><a href="#cb12-27" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb12-28"><a href="#cb12-28" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb12-29"><a href="#cb12-29" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-30"><a href="#cb12-30" aria-hidden="true" tabindex="-1"></a><span class="kw">try</span> save_png(&amp;image_header, buffer[<span class="dv">0</span>..]);</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</div>
<p>After we execute this <code>save_png()</code> function, we should have a new PNG file inside our CWD, named <code>pedro_pascal_filter.png</code>. If we open this PNG file, we will see the same image exposed in <a href="#fig-pascal-gray" class="quarto-xref">Figure&nbsp;<span>15.2</span></a>.</p>
</section>
<section id="building-our-project" class="level2" data-number="15.7">
<h2 data-number="15.7" class="anchored" data-anchor-id="building-our-project"><span class="header-section-number">15.7</span> Building our project</h2>
<p>Now that we have written the code, let’s discuss how can we build/compile this project. To do that, I’m going to create a <code>build.zig</code> file in the root directory of our project, and start writing the necessary code to compile the project, using the knowledge that we have acquired from <a href="07-build-system.html" class="quarto-xref"><span>Chapter 9</span></a>.</p>
<p>We first create the build target for our executable file, that executes our Zig code. Let’s suppose that all of our Zig code was written into a Zig module named <code>image_filter.zig</code>. The <code>exe</code> object exposed in the build script below describes the build target for our executable file.</p>
<p>Since we have used some C code from the <code>libspng</code> library in our Zig code, we need to link our Zig code (which is in the <code>exe</code> build target) to both the C Standard Library, and, to the <code>libspng</code> library. We do that, by calling the <code>linkLibC()</code> and <code>linkSystemLibrary()</code> methods from our <code>exe</code> build target.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb13"><pre class="sourceCode zig code-with-copy"><code class="sourceCode zig"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> std = <span class="bu">@import</span>(<span class="st">"std"</span>);</span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a><span class="kw">pub</span> <span class="kw">fn</span> build(b: *std.Build) <span class="dt">void</span> <span class="op">{</span></span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a>    <span class="kw">const</span> exe = b.addExecutable(.<span class="op">{</span></span>
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a>        .name = <span class="st">"image_filter"</span>,</span>
<span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a>        .root_module = b.createModule(.<span class="op">{</span></span>
<span id="cb13-6"><a href="#cb13-6" aria-hidden="true" tabindex="-1"></a>            .root_source_file = b.path(<span class="st">"src/image_filter.zig"</span>),</span>
<span id="cb13-7"><a href="#cb13-7" aria-hidden="true" tabindex="-1"></a>            .target = b.graph.host,</span>
<span id="cb13-8"><a href="#cb13-8" aria-hidden="true" tabindex="-1"></a>            .link_libc = <span class="cn">true</span></span>
<span id="cb13-9"><a href="#cb13-9" aria-hidden="true" tabindex="-1"></a>        <span class="op">}</span>)</span>
<span id="cb13-10"><a href="#cb13-10" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span>);</span>
<span id="cb13-11"><a href="#cb13-11" aria-hidden="true" tabindex="-1"></a>    <span class="co">// Link to libspng library:</span></span>
<span id="cb13-12"><a href="#cb13-12" aria-hidden="true" tabindex="-1"></a>    exe.root_module.linkSystemLibrary(<span class="st">"spng"</span>, .<span class="op">{}</span>);</span>
<span id="cb13-13"><a href="#cb13-13" aria-hidden="true" tabindex="-1"></a>    <span class="co">// Link to math library:</span></span>
<span id="cb13-14"><a href="#cb13-14" aria-hidden="true" tabindex="-1"></a>    exe.root_module.linkSystemLibrary(<span class="st">"m"</span>, .<span class="op">{}</span>);</span>
<span id="cb13-15"><a href="#cb13-15" aria-hidden="true" tabindex="-1"></a>    b.installArtifact(exe);</span>
<span id="cb13-16"><a href="#cb13-16" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</div>
<p>Since we are using the <code>linkSystemLibrary()</code> method, it means that the library files for <code>libspng</code> are searched in your system to be linked with the <code>exe</code> build target. If you have not yet built and installed the <code>libspng</code> library into your system, this linkage step will likely not work. Because it will not find the library files in your system.</p>
<p>So, just remember to install <code>libspng</code> in your system, if you want to build this project. Having this build script above written, we can finally build our project by running the <code>zig build</code> command in the terminal.</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="ex">zig</span> build</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>


<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0" role="list" style="display: none">
<div id="ref-wiki_grayscale" class="csl-entry" role="listitem">
Wikipedia. 2024. <span>“Grayscale.”</span> Wikipedia. <a href="https://en.wikipedia.org/wiki/Grayscale">https://en.wikipedia.org/wiki/Grayscale</a>.
</div>
</div>
</section>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr>
<ol>
<li id="fn1"><p><a href="https://github.com/pedropark99/zig-book/tree/main/ZigExamples/image_filter" class="uri">https://github.com/pedropark99/zig-book/tree/main/ZigExamples/image_filter</a><a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p><a href="https://library.si.edu/exhibition/color-in-a-new-light/science" class="uri">https://library.si.edu/exhibition/color-in-a-new-light/science</a><a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn3"><p><a href="https://github.com/pedropark99/zig-book/tree/main/ZigExamples/image_filter" class="uri">https://github.com/pedropark99/zig-book/tree/main/ZigExamples/image_filter</a><a href="#fnref3" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn4"><p><a href="https://libspng.org/" class="uri">https://libspng.org/</a><a href="#fnref4" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn5"><p><a href="https://libspng.org/docs/build/" class="uri">https://libspng.org/docs/build/</a><a href="#fnref5" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>

</main> <!-- /main -->
<script id="quarto-html-after-body" type="application/javascript">
  window.document.addEventListener("DOMContentLoaded", function (event) {
    // Ensure there is a toggle, if there isn't float one in the top right
    if (window.document.querySelector('.quarto-color-scheme-toggle') === null) {
      const a = window.document.createElement('a');
      a.classList.add('top-right');
      a.classList.add('quarto-color-scheme-toggle');
      a.href = "";
      a.onclick = function() { try { window.quartoToggleColorScheme(); } catch {} return false; };
      const i = window.document.createElement("i");
      i.classList.add('bi');
      a.appendChild(i);
      window.document.body.appendChild(a);
    }
    setColorSchemeToggle(hasAlternateSentinel())
    const icon = "";
    const anchorJS = new window.AnchorJS();
    anchorJS.options = {
      placement: 'right',
      icon: icon
    };
    anchorJS.add('.anchored');
    const isCodeAnnotation = (el) => {
      for (const clz of el.classList) {
        if (clz.startsWith('code-annotation-')) {                     
          return true;
        }
      }
      return false;
    }
    const onCopySuccess = function(e) {
      // button target
      const button = e.trigger;
      // don't keep focus
      button.blur();
      // flash "checked"
      button.classList.add('code-copy-button-checked');
      var currentTitle = button.getAttribute("title");
      button.setAttribute("title", "Copied!");
      let tooltip;
      if (window.bootstrap) {
        button.setAttribute("data-bs-toggle", "tooltip");
        button.setAttribute("data-bs-placement", "left");
        button.setAttribute("data-bs-title", "Copied!");
        tooltip = new bootstrap.Tooltip(button, 
          { trigger: "manual", 
            customClass: "code-copy-button-tooltip",
            offset: [0, -8]});
        tooltip.show();    
      }
      setTimeout(function() {
        if (tooltip) {
          tooltip.hide();
          button.removeAttribute("data-bs-title");
          button.removeAttribute("data-bs-toggle");
          button.removeAttribute("data-bs-placement");
        }
        button.setAttribute("title", currentTitle);
        button.classList.remove('code-copy-button-checked');
      }, 1000);
      // clear code selection
      e.clearSelection();
    }
    const getTextToCopy = function(trigger) {
        const codeEl = trigger.previousElementSibling.cloneNode(true);
        for (const childEl of codeEl.children) {
          if (isCodeAnnotation(childEl)) {
            childEl.remove();
          }
        }
        return codeEl.innerText;
    }
    const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', {
      text: getTextToCopy
    });
    clipboard.on('success', onCopySuccess);
    if (window.document.getElementById('quarto-embedded-source-code-modal')) {
      const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', {
        text: getTextToCopy,
        container: window.document.getElementById('quarto-embedded-source-code-modal')
      });
      clipboardModal.on('success', onCopySuccess);
    }
      var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//);
      var mailtoRegex = new RegExp(/^mailto:/);
        var filterRegex = new RegExp('/' + window.location.host + '/');
      var isInternal = (href) => {
          return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href);
      }
      // Inspect non-navigation links and adorn them if external
     var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)');
      for (var i=0; i<links.length; i++) {
        const link = links[i];
        if (!isInternal(link.href)) {
          // undo the damage that might have been done by quarto-nav.js in the case of
          // links that we want to consider external
          if (link.dataset.originalHref !== undefined) {
            link.href = link.dataset.originalHref;
          }
        }
      }
    function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) {
      const config = {
        allowHTML: true,
        maxWidth: 500,
        delay: 100,
        arrow: false,
        appendTo: function(el) {
            return el.parentElement;
        },
        interactive: true,
        interactiveBorder: 10,
        theme: 'quarto',
        placement: 'bottom-start',
      };
      if (contentFn) {
        config.content = contentFn;
      }
      if (onTriggerFn) {
        config.onTrigger = onTriggerFn;
      }
      if (onUntriggerFn) {
        config.onUntrigger = onUntriggerFn;
      }
      window.tippy(el, config); 
    }
    const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]');
    for (var i=0; i<noterefs.length; i++) {
      const ref = noterefs[i];
      tippyHover(ref, function() {
        // use id or data attribute instead here
        let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href');
        try { href = new URL(href).hash; } catch {}
        const id = href.replace(/^#\/?/, "");
        const note = window.document.getElementById(id);
        if (note) {
          return note.innerHTML;
        } else {
          return "";
        }
      });
    }
    const xrefs = window.document.querySelectorAll('a.quarto-xref');
    const processXRef = (id, note) => {
      // Strip column container classes
      const stripColumnClz = (el) => {
        el.classList.remove("page-full", "page-columns");
        if (el.children) {
          for (const child of el.children) {
            stripColumnClz(child);
          }
        }
      }
      stripColumnClz(note)
      if (id === null || id.startsWith('sec-')) {
        // Special case sections, only their first couple elements
        const container = document.createElement("div");
        if (note.children && note.children.length > 2) {
          container.appendChild(note.children[0].cloneNode(true));
          for (let i = 1; i < note.children.length; i++) {
            const child = note.children[i];
            if (child.tagName === "P" && child.innerText === "") {
              continue;
            } else {
              container.appendChild(child.cloneNode(true));
              break;
            }
          }
          if (window.Quarto?.typesetMath) {
            window.Quarto.typesetMath(container);
          }
          return container.innerHTML
        } else {
          if (window.Quarto?.typesetMath) {
            window.Quarto.typesetMath(note);
          }
          return note.innerHTML;
        }
      } else {
        // Remove any anchor links if they are present
        const anchorLink = note.querySelector('a.anchorjs-link');
        if (anchorLink) {
          anchorLink.remove();
        }
        if (window.Quarto?.typesetMath) {
          window.Quarto.typesetMath(note);
        }
        if (note.classList.contains("callout")) {
          return note.outerHTML;
        } else {
          return note.innerHTML;
        }
      }
    }
    for (var i=0; i<xrefs.length; i++) {
      const xref = xrefs[i];
      tippyHover(xref, undefined, function(instance) {
        instance.disable();
        let url = xref.getAttribute('href');
        let hash = undefined; 
        if (url.startsWith('#')) {
          hash = url;
        } else {
          try { hash = new URL(url).hash; } catch {}
        }
        if (hash) {
          const id = hash.replace(/^#\/?/, "");
          const note = window.document.getElementById(id);
          if (note !== null) {
            try {
              const html = processXRef(id, note.cloneNode(true));
              instance.setContent(html);
            } finally {
              instance.enable();
              instance.show();
            }
          } else {
            // See if we can fetch this
            fetch(url.split('#')[0])
            .then(res => res.text())
            .then(html => {
              const parser = new DOMParser();
              const htmlDoc = parser.parseFromString(html, "text/html");
              const note = htmlDoc.getElementById(id);
              if (note !== null) {
                const html = processXRef(id, note);
                instance.setContent(html);
              } 
            }).finally(() => {
              instance.enable();
              instance.show();
            });
          }
        } else {
          // See if we can fetch a full url (with no hash to target)
          // This is a special case and we should probably do some content thinning / targeting
          fetch(url)
          .then(res => res.text())
          .then(html => {
            const parser = new DOMParser();
            const htmlDoc = parser.parseFromString(html, "text/html");
            const note = htmlDoc.querySelector('main.content');
            if (note !== null) {
              // This should only happen for chapter cross references
              // (since there is no id in the URL)
              // remove the first header
              if (note.children.length > 0 && note.children[0].tagName === "HEADER") {
                note.children[0].remove();
              }
              const html = processXRef(null, note);
              instance.setContent(html);
            } 
          }).finally(() => {
            instance.enable();
            instance.show();
          });
        }
      }, function(instance) {
      });
    }
        let selectedAnnoteEl;
        const selectorForAnnotation = ( cell, annotation) => {
          let cellAttr = 'data-code-cell="' + cell + '"';
          let lineAttr = 'data-code-annotation="' +  annotation + '"';
          const selector = 'span[' + cellAttr + '][' + lineAttr + ']';
          return selector;
        }
        const selectCodeLines = (annoteEl) => {
          const doc = window.document;
          const targetCell = annoteEl.getAttribute("data-target-cell");
          const targetAnnotation = annoteEl.getAttribute("data-target-annotation");
          const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation));
          const lines = annoteSpan.getAttribute("data-code-lines").split(",");
          const lineIds = lines.map((line) => {
            return targetCell + "-" + line;
          })
          let top = null;
          let height = null;
          let parent = null;
          if (lineIds.length > 0) {
              //compute the position of the single el (top and bottom and make a div)
              const el = window.document.getElementById(lineIds[0]);
              top = el.offsetTop;
              height = el.offsetHeight;
              parent = el.parentElement.parentElement;
            if (lineIds.length > 1) {
              const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]);
              const bottom = lastEl.offsetTop + lastEl.offsetHeight;
              height = bottom - top;
            }
            if (top !== null && height !== null && parent !== null) {
              // cook up a div (if necessary) and position it 
              let div = window.document.getElementById("code-annotation-line-highlight");
              if (div === null) {
                div = window.document.createElement("div");
                div.setAttribute("id", "code-annotation-line-highlight");
                div.style.position = 'absolute';
                parent.appendChild(div);
              }
              div.style.top = top - 2 + "px";
              div.style.height = height + 4 + "px";
              div.style.left = 0;
              let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter");
              if (gutterDiv === null) {
                gutterDiv = window.document.createElement("div");
                gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter");
                gutterDiv.style.position = 'absolute';
                const codeCell = window.document.getElementById(targetCell);
                const gutter = codeCell.querySelector('.code-annotation-gutter');
                gutter.appendChild(gutterDiv);
              }
              gutterDiv.style.top = top - 2 + "px";
              gutterDiv.style.height = height + 4 + "px";
            }
            selectedAnnoteEl = annoteEl;
          }
        };
        const unselectCodeLines = () => {
          const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"];
          elementsIds.forEach((elId) => {
            const div = window.document.getElementById(elId);
            if (div) {
              div.remove();
            }
          });
          selectedAnnoteEl = undefined;
        };
          // Handle positioning of the toggle
      window.addEventListener(
        "resize",
        throttle(() => {
          elRect = undefined;
          if (selectedAnnoteEl) {
            selectCodeLines(selectedAnnoteEl);
          }
        }, 10)
      );
      function throttle(fn, ms) {
      let throttle = false;
      let timer;
        return (...args) => {
          if(!throttle) { // first call gets through
              fn.apply(this, args);
              throttle = true;
          } else { // all the others get throttled
              if(timer) clearTimeout(timer); // cancel #2
              timer = setTimeout(() => {
                fn.apply(this, args);
                timer = throttle = false;
              }, ms);
          }
        };
      }
        // Attach click handler to the DT
        const annoteDls = window.document.querySelectorAll('dt[data-target-cell]');
        for (const annoteDlNode of annoteDls) {
          annoteDlNode.addEventListener('click', (event) => {
            const clickedEl = event.target;
            if (clickedEl !== selectedAnnoteEl) {
              unselectCodeLines();
              const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active');
              if (activeEl) {
                activeEl.classList.remove('code-annotation-active');
              }
              selectCodeLines(clickedEl);
              clickedEl.classList.add('code-annotation-active');
            } else {
              // Unselect the line
              unselectCodeLines();
              clickedEl.classList.remove('code-annotation-active');
            }
          });
        }
    const findCites = (el) => {
      const parentEl = el.parentElement;
      if (parentEl) {
        const cites = parentEl.dataset.cites;
        if (cites) {
          return {
            el,
            cites: cites.split(' ')
          };
        } else {
          return findCites(el.parentElement)
        }
      } else {
        return undefined;
      }
    };
    var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]');
    for (var i=0; i<bibliorefs.length; i++) {
      const ref = bibliorefs[i];
      const citeInfo = findCites(ref);
      if (citeInfo) {
        tippyHover(citeInfo.el, function() {
          var popup = window.document.createElement('div');
          citeInfo.cites.forEach(function(cite) {
            var citeDiv = window.document.createElement('div');
            citeDiv.classList.add('hanging-indent');
            citeDiv.classList.add('csl-entry');
            var biblioDiv = window.document.getElementById('ref-' + cite);
            if (biblioDiv) {
              citeDiv.innerHTML = biblioDiv.innerHTML;
            }
            popup.appendChild(citeDiv);
          });
          return popup.innerHTML;
        });
      }
    }
  });
  </script>
<nav class="page-navigation">
  <div class="nav-page nav-page-previous">
      <a href="../Chapters/14-zig-c-interop.html" class="pagination-link" aria-label="Zig interoperability with C">
        <i class="bi bi-arrow-left-short"></i> <span class="nav-page-text"><span class="chapter-number">14</span>&nbsp; <span class="chapter-title">Zig interoperability with C</span></span>
      </a>          
  </div>
  <div class="nav-page nav-page-next">
      <a href="../Chapters/14-threads.html" class="pagination-link" aria-label="Introducing threads and parallelism in Zig">
        <span class="nav-page-text"><span class="chapter-number">16</span>&nbsp; <span class="chapter-title">Introducing threads and parallelism in Zig</span></span> <i class="bi bi-arrow-right-short"></i>
      </a>
  </div>
</nav>
</div> <!-- /content -->




</body></html>